using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Zsa.Reflection.Discovery
{
    /// <summary>
    /// Type search parameter.
    /// </summary>
    public struct TypeSearchParameter
    {

        #region Fields
        /// <summary>
        /// The parameter.
        /// </summary>
        private object _Parameter;

        /// <summary>
        /// The type filter.
        /// </summary>
        //private readonly TypeFilter _TypeFilter;
        private readonly TypeSearchParameterType _SearchType;
        #endregion Fields

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="TypeSearchParameter"/> class.
        /// </summary>
        /// <param name="searchType">Type of the search.</param>
        /// <param name="arg">The arg.</param>
        public TypeSearchParameter(TypeSearchParameterType searchType,
            object arg)
        {
            object _TempArg = TypeSearchParameter.CheckParameterValue(searchType, arg);
            _SearchType = searchType;
            _Parameter = _TempArg;
        }
        #endregion Constructor

        #region Properties
        /// <summary>
        /// Gets the type of the parameter.
        /// </summary>
        /// <value>The type of the parameter.</value>
        public TypeSearchParameterType ParameterType
        {
            get
            {
                return _SearchType;
            }
        }

        /// <summary>
        /// Gets the parameter.
        /// </summary>
        /// <value>The parameter.</value>
        public object Parameter
        {
            get
            {
                return _Parameter;
            }
            set
            {
                if (value == _Parameter)
                {
                    return;
                }
                object _TempArg = TypeSearchParameter.CheckParameterValue(_SearchType, value);
                _Parameter = _TempArg;
            }
        }
        #endregion Properties

        #region Methods

        /// <summary>
        /// Determines whether the specified type is valid.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>
        /// 	<c>true</c> if the specified type is valid; otherwise, <c>false</c>.
        /// </returns>
        public bool IsValid(Type type)
        {
            TypeFilter _Filter = null;
            switch (_SearchType)
            {
                case TypeSearchParameterType.HasAttrbiute:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterHasAttribute);
                    break;
                case TypeSearchParameterType.HasAttributeInherit:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterHasAttributeInherit);
                    break;
                case TypeSearchParameterType.ImplementsInterface:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterImplementsInterface);
                    break;
                case TypeSearchParameterType.InheiritsFrom:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterInherits);
                    break;
                case TypeSearchParameterType.MemberTypeEquals:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterMemberTypeEquals);
                    break;
                case TypeSearchParameterType.NameContains:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterNameContains);
                    break;
                case TypeSearchParameterType.NameEquals:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterNameEquals);
                    break;
                case TypeSearchParameterType.NamespaceEquals:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterNamespaceEquals);
                    break;
                case TypeSearchParameterType.NamespaceIn:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterNamespaceIn);
                    break;
                case TypeSearchParameterType.TypeAttributesEqual:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterTypeAttributesEqual);
                    break;
                case TypeSearchParameterType.TypeAttributesContains:
                    _Filter = new TypeFilter(TypeSearchParameter.TypeFilterTypeAttributesContains);
                    break;
                default:
                    throw new NotImplementedException();
            }
            return _Filter(type, _Parameter);
        }

        #region Static

        /// <summary>
        /// Checks the parameter value.
        /// </summary>
        /// <param name="searchParameterType">Type of the search parameter.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private static object CheckParameterValue(TypeSearchParameterType searchParameterType,
            object value)
        {
            // The type.
            Object _ReturnValue = null;
            Type _Type = null;

            switch (searchParameterType)
            {
                // The binding flags parameter.                        
                case TypeSearchParameterType.TypeAttributesEqual:
                    if (value == null)
                    {
                        _ReturnValue = BindingFlags.Default;
                    }
                    else if (value is BindingFlags)
                    {
                        _ReturnValue = value;
                    }
                    else
                    {
                        throw new ArgumentException("The value argument is of a invalid type !", "value");
                    }
                    break;

                // The has attribute parameters.                        
                case TypeSearchParameterType.HasAttrbiute:
                case TypeSearchParameterType.HasAttributeInherit:
                    // Get the parameter type.
                    _Type = GetTypeParameterValue(value);

                    // Make sure this is an attribute.
                    if (!_Type.IsSubclassOf(typeof(Attribute)))
                    {
                        throw new ArgumentException("The Type must be a class that is derived from System.Attribute !", "value");
                    }
                    // Set the value.
                    _ReturnValue = _Type;
                    break;

                // Implements interface attribute.
                case TypeSearchParameterType.ImplementsInterface:
                    // Get the parameter type.
                    _Type = GetTypeParameterValue(value);

                    // Make sure the type is an interface.
                    if (!_Type.IsInterface)
                    {
                        throw new ArgumentException("The Type must be of an interface !", "value");
                    }
                    // Set the value.
                    _ReturnValue = _Type;
                    break;
                // The inherits from attribute.
                case TypeSearchParameterType.InheiritsFrom:
                    // Get the parameter type.
                    _Type = GetTypeParameterValue(value);

                    // Make sure the type is an interface.
                    if (!_Type.IsClass)
                    {
                        throw new ArgumentException("The Type must be a class !", "value");
                    }
                    // Make sure the class is not sealed.
                    if (_Type.IsSealed)
                    {
                        throw new ArgumentException("The Type cannot be a sealed class !", "value");
                    }
                    //Set the value.
                    _ReturnValue = _Type;
                    break;

                // The member type attribute.
                case TypeSearchParameterType.MemberTypeEquals:

                    // Must specify the member type.
                    if (value == null)
                    {
                        throw new ArgumentNullException("value", "Must specify the member type. !");
                    }

                    // The paraameter value must be of a member type.
                    if (value is MemberTypes)
                    {
                        _ReturnValue = value;
                    }
                    else
                    {
                        throw new ArgumentException("The value argument is of a invalid type !", "value");
                    }
                    break;
                case TypeSearchParameterType.NameContains:
                case TypeSearchParameterType.NameEquals:
                case TypeSearchParameterType.NamespaceEquals:
                case TypeSearchParameterType.NamespaceIn:
                    if (value == null)
                    {
                        throw new ArgumentNullException("value", "The value cannot be null !");
                    }
                    if (!(value is string))
                    {
                        throw new ArgumentException("The value argument is of a invalid type !", "value");
                    }
                    _ReturnValue = value;
                    break;
                default:
                    throw new InvalidOperationException();
            }
            return _ReturnValue;
        }

        /// <summary>
        /// Gets the type parameter value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private static Type GetTypeParameterValue(object value)
        {
            // The attribute type/type name cannot be null.
            if (value == null)
            {
                throw new ArgumentNullException("value", "Must specify the attribute type. !");
            }

            // Try to cast the value to type.
            Type _Type = value as Type;
            // If the type is null then we check is string.
            if (_Type == null)
            {
                // Try to get type from string. Throw error if bad.
                if (value is string)
                {
                    _Type = Type.GetType(value.ToString(), true);
                }
                else // Throw excep.
                {
                    throw new ArgumentException("The value argument is of a invalid type !", "value");
                }

            }

            // Return the type.
            return _Type;
        }

        #region Filter Methods

        private static bool TypeFilterHasAttributeInherit(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            if (!(filter is Type))
            {
                throw new ArgumentException("filter", "The filter argument me be a type !");
            }
            Type _Type = filter as Type;
            return type.GetCustomAttributes(_Type, true).Length > 0;
        }
        
        private static bool TypeFilterHasAttribute(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            if (!(filter is Type))
            {
                throw new ArgumentException("filter", "The filter argument me be a type !");
            }
            Type _Type = filter as Type;
            return type.GetCustomAttributes(_Type, false).Length > 0;
        }

        private static bool TypeFilterMemberTypeEquals(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            if (!(filter is MemberTypes))
            {
                throw new ArgumentException("filter", "The filter argument me be a member type !");
            }
            MemberTypes _Type = (MemberTypes)filter;
            return (type.MemberType & _Type) == _Type;
        }

        private static bool TypeFilterNamespaceEquals(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            // Some stuff has no namespace.  so we check for that.
            string _TypeNamespace = type.Namespace;
            string _Filter = filter.ToString();
            if (string.IsNullOrEmpty(_Filter))
            {
                return false;
            }

            if (string.IsNullOrEmpty(_TypeNamespace))
            {
                return false;
            }
            return _TypeNamespace.Equals(_Filter);

        }

        private static bool TypeFilterNamespaceIn(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            string _Filter = filter.ToString();
            string _TypeNamespace = type.Namespace;
            if ( string.IsNullOrEmpty(_Filter))
            {
                return false;
            }

            if (string.IsNullOrEmpty(_TypeNamespace))
            {
                return false;
            }
            return _TypeNamespace.StartsWith(_Filter + ".") ||
                _TypeNamespace == _Filter;
        }

        private static bool TypeFilterNameEquals(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            return type.Name.Equals(filter.ToString());
        }

        private static bool TypeFilterNameContains(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            return type.Name.Contains(filter.ToString());
        }

        private static bool TypeFilterImplementsInterface(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            if (!(filter is Type))
            {
                throw new ArgumentException("filter", "The filter argument me be a type !");
            }
            Type _InterfaceType = filter as Type;
            Type[] _Types = type.GetInterfaces();
            for ( int _TempIndex = 0; _TempIndex < _Types.Length; _TempIndex++ )
            {
                if ( _Types[_TempIndex] == _InterfaceType )
                {
                    return true;
                }
            }
            return false;
        }

        private static bool TypeFilterInherits(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            if (!(filter is Type))
            {
                throw new ArgumentException("filter", "The filter argument me be a type !");
            }
            Type _Type = filter as Type;
            return type.IsSubclassOf(_Type);
        }

        private static bool TypeFilterTypeAttributesEqual(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            if (!(filter is BindingFlags))
            {
                throw new ArgumentException("filter", "The filter argument me be a TypeAttributes type !");
            }
            TypeAttributes _Flags = (TypeAttributes)filter;
            return type.Attributes == _Flags;
        }

        private static bool TypeFilterTypeAttributesContains(Type type, object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            if (!(filter is BindingFlags))
            {
                throw new ArgumentException("filter", "The filter argument me be a TypeAttributes type !");
            }
            TypeAttributes _Flags = (TypeAttributes)filter;
            return (type.Attributes & _Flags) == _Flags;
        }

        private static bool ConstructorFilterHasSignature(ConstructorInfo type, 
            object filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", "The filter argument cannot be null !");
            }
            if (!(filter is Type))
            {
                throw new ArgumentException("filter", "The filter argument me be a type !");
            }
            Type _Type = filter as Type;
            return type.GetCustomAttributes(_Type, true).Length > 0;
        }

        private static bool AttributeFilterPropertyEquals(CustomAttributeData attriubteData, object filter)
        {
            throw new NotImplementedException();
        }
        #endregion Filter Methods

        #endregion Static

        #endregion Methods
    }
}
